package sqladvisor

import (
	"fmt"

	"github.com/pingcap/errors"
	"github.com/romberli/go-util/constant"
	"github.com/romberli/log"
	"github.com/spf13/viper"

	"github.com/romberli/das/config"
	"github.com/romberli/das/internal/app/metadata"
	"github.com/romberli/das/internal/dependency/sqladvisor"
	"github.com/romberli/das/pkg/util/deployment"
	utiladvisor "github.com/romberli/das/pkg/util/sqladvisor"
)

var _ sqladvisor.Service = (*Service)(nil)

type Service struct {
	sqladvisor.Repository
	Advisor sqladvisor.Advisor
	Advice  string `json:"advice"`
	Message string `json:"message"`
}

// NewService returns a new *Service
func NewService(soarBin, configFile string) sqladvisor.Service {
	return newService(soarBin, configFile)
}

// NewServiceWithDefault returns a new *Service with default value
func NewServiceWithDefault() sqladvisor.Service {
	soarBin := viper.GetString(config.SQLAdvisorSoarBinKey)
	configFile := viper.GetString(config.SQLAdvisorSoarConfigKey)

	return newService(soarBin, configFile)
}

// newService returns a new *Service
func newService(soarBin, configFile string) *Service {
	return &Service{
		Repository: NewRepositoryWithGlobal(),
		Advisor:    NewDefaultAdvisor(soarBin, configFile),
	}
}

// GetFingerprint returns the fingerprint of the sql text
func (s *Service) GetFingerprint(sqlText string) string {
	return s.Advisor.GetFingerprint(sqlText)
}

// GetSQLID returns the identity of the sql text
func (s *Service) GetSQLID(sqlText string) string {
	return s.Advisor.GetSQLID(sqlText)
}

// AdviseByDBIDWithHTTP parses the sql text and returns the tuning advice,
// note that only the first sql statement in the sql text will be advised
func (s *Service) AdviseByDBIDWithHTTP(dbID int, sqlText, loginName string) (string, error) {
	sqlList, err := s.Advisor.GetParser().Split(sqlText)
	if err != nil {
		return constant.EmptyString, err
	}

	if len(sqlList) == constant.ZeroInt {
		return constant.EmptyString, errors.Errorf("sqladvisor AdviseByDBIDWithHTTP(): sql text is empty")
	}

	dbService := metadata.NewDBServiceWithDefault()
	err = dbService.GetByID(dbID)
	if err != nil {
		return constant.EmptyString, err
	}
	db := dbService.GetDBs()[constant.ZeroInt]
	masterServers, err := db.GetMasterServersWithHTTP()
	if err != nil {
		return constant.EmptyString, err
	}
	masterServer := masterServers[constant.ZeroInt]

	return s.adviseByHostInfoAndDBNameWithHTTP(masterServer.GetHostIP(), masterServer.GetPortNum(), db.GetDBName(), sqlList[constant.ZeroInt], loginName)
}

// AdviseByHostInfoAndDBNameWithHTTP parses the sql text with given mysql server host info and db name, then returns the tuning advice,
// note that only the first sql statement in the sql text will be advised
func (s *Service) AdviseByHostInfoAndDBNameWithHTTP(hostIP string, portNum int, dbName, sqlText, loginName string) (string, error) {
	return s.adviseByHostInfoAndDBNameWithHTTP(hostIP, portNum, dbName, sqlText, loginName)
}

// adviseByHostInfoAndDBNameWithHTTP parses the sql text with given mysql server host info and db name, then returns the tuning advice,
// note that only the first sql statement in the sql text will be advised
func (s *Service) adviseByHostInfoAndDBNameWithHTTP(hostIP string, portNum int, dbName, sqlText, loginName string) (string, error) {
	sqlList, err := s.Advisor.GetParser().Split(sqlText)
	if err != nil {
		return constant.EmptyString, err
	}

	if len(sqlList) == constant.ZeroInt {
		return constant.EmptyString, errors.Errorf("sqladvisor AdviseByAdviseByHostInfoAndDBNameDBID(): sql text is empty")
	}
	sql := sqlList[constant.ZeroInt]
	if viper.GetString(config.RoleKey) == config.ServerRole {
		mysqlServerService := metadata.NewMySQLServerServiceWithDefault()
		err = mysqlServerService.GetByHostInfo(hostIP, portNum)
		if err != nil {
			return constant.EmptyString, err
		}
		mysqlServer := mysqlServerService.GetMySQLServers()[constant.ZeroInt]
		deploymentService := deployment.NewServiceWithDefault(mysqlServer)
		if !deploymentService.CheckAccessibility() {
			// prepare data
			data := []byte(fmt.Sprintf(utiladvisor.AdviseByHostInfoAndDBNameHTTPBodyTemplate, hostIP, portNum, dbName, sql, loginName))
			// send request
			respBody, err := deploymentService.PostDAS(utiladvisor.AdviseByHostInfoAndDBNamePath, data)
			if err != nil {
				return constant.EmptyString, err
			}

			return string(respBody), nil
		}
	}

	return s.AdviseByHostInfoAndDBNameLocal(hostIP, portNum, dbName, sql)
}

// AdviseByHostInfoAndDBNameLocal parses the sql text with given mysql server host info and db name, then returns the tuning advice,
// note that only the first sql statement in the sql text will be advised
func (s *Service) AdviseByHostInfoAndDBNameLocal(hostIP string, portNum int, dbName, sql string) (string, error) {
	advice, msg, err := s.Advisor.AdviseByHostInfoAndDBName(hostIP, portNum, dbName, sql)
	if err != nil {
		return constant.EmptyString, err
	}

	if msg != constant.EmptyString {
		log.Infof("sqladvisor AdviseByAdviseByHostInfoAndDBNameDBID(): advisor message: %s", msg)
	}

	return advice, nil
}
